package com.mstar.qs.common.jmx;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;

import javax.management.Attribute;
import javax.management.AttributeChangeNotification;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.RuntimeOperationsException;
import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.management.modelmbean.ModelMBean;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanConstructorInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanNotificationInfo;
import javax.management.modelmbean.ModelMBeanOperationInfo;

import ognl.ExpressionSyntaxException;
import ognl.InappropriateExpressionException;
import ognl.NoSuchPropertyException;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.TypeConverter;

import org.apache.log4j.Logger;
import org.apache.poi.hssf.record.formula.functions.T;

public class ObjectMBean<T> implements ModelMBean, MBeanRegistration {
	

	/**
	 * Creates a new instance with the specified POJO.
	 */
	public ObjectMBean(T source) {
		if (source == null) {
			throw new NullPointerException("source");
		}

		this.source = source;

		this.info = createModelMBeanInfo(source);
	}
	
	public ObjectMBean(T source,String[] exceptOperations,String[] exceptionAttributes) {
		if (source == null) {
			throw new NullPointerException("source");
		}

		this.source = source;

		if(exceptOperations!=null) {
		for(int i=0;i < exceptOperations.length; i++) {
			this.exceptOperations.add(exceptOperations[i]);
		}
		}
		if(exceptionAttributes!=null) {
		for(int j=0;j<exceptionAttributes.length; j++) {
			this.exceptAttributes.add(exceptionAttributes[j]);
		}
		}
		
		this.info = createModelMBeanInfo(source);
		
	}
	
	private Set<String> exceptOperations=new HashSet<String>();
	private Set<String> exceptAttributes=new HashSet<String>();
	private static final Map<ObjectName, Object> sources = new ConcurrentHashMap<ObjectName, Object>();
	private final T source;
	private final MBeanInfo info;
	private final Map<String, PropertyDescriptor> propertyDescriptors = new HashMap<String, PropertyDescriptor>();
	private final TypeConverter typeConverter = new OgnlTypeConverter();

	private volatile MBeanServer server;
	private volatile ObjectName name;
	
	
	public static Object getSource(ObjectName oname) {
		return sources.get(oname);
	}

	protected final static Logger LOGGER = Logger.getLogger(ObjectMBean.class.getPackage().getName());

	

	

	public final Object getAttribute(String fqan) throws AttributeNotFoundException, MBeanException, ReflectionException {
		try {
			return convertValue(source.getClass(), fqan, getAttribute0(fqan), false);
		} catch (AttributeNotFoundException e) {
			// Do nothing
		} catch (Throwable e) {
			throwMBeanException(e);
		}

		// Check if the attribute exist, if not throw an exception
		PropertyDescriptor pdesc = propertyDescriptors.get(fqan);
		if (pdesc == null) {
			throwMBeanException(new IllegalArgumentException("Unknown attribute: " + fqan));
		}

		try {

			Object parent = getParent(fqan);
			boolean writable = isWritable(source.getClass(), pdesc);

			return convertValue(parent.getClass(), getLeafAttributeName(fqan), getAttribute(source, fqan, pdesc.getPropertyType()), writable);
		} catch (Throwable e) {
			throwMBeanException(e);
		}

		throw new IllegalStateException();
	}

	public final void setAttribute(Attribute attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
		String aname = attribute.getName();
		Object avalue = attribute.getValue();

		try {
			setAttribute0(aname, avalue);
		} catch (AttributeNotFoundException e) {
			// Do nothing
		} catch (Throwable e) {
			throwMBeanException(e);
		}

		PropertyDescriptor pdesc = propertyDescriptors.get(aname);
		if (pdesc == null) {
			throwMBeanException(new IllegalArgumentException("Unknown attribute: " + aname));
		}

		try {
			PropertyEditor e = getPropertyEditor(getParent(aname).getClass(), pdesc.getName(), pdesc.getPropertyType());
			e.setAsText((String) avalue);
			OgnlContext ctx = (OgnlContext) Ognl.createDefaultContext(source);
			ctx.setTypeConverter(typeConverter);
			Ognl.setValue(aname, ctx, source, e.getValue());
		} catch (Throwable e) {
			throwMBeanException(e);
		}
	}

	public final Object invoke(String name, Object params[], String signature[]) throws MBeanException, ReflectionException {

		// Handle synthetic operations first.
		if (name.equals("unregisterMBean")) {
			try {
				server.unregisterMBean(this.name);
				return null;
			} catch (InstanceNotFoundException e) {
				throwMBeanException(e);
			}
		}

		try {
			return convertValue(null, null, invoke0(name, params, signature), false);
		} catch (NoSuchMethodException e) {
			// Do nothing
		} catch (Throwable e) {
			throwMBeanException(e);
		}

		// And then try reflection.
		Class<?>[] paramTypes = new Class[signature.length];
		for (int i = 0; i < paramTypes.length; i++) {
			try {
				paramTypes[i] = getAttributeClass(signature[i]);
			} catch (ClassNotFoundException e) {
				throwMBeanException(e);
			}

			PropertyEditor e = getPropertyEditor(source.getClass(), "p" + i, paramTypes[i]);
			if (e == null) {
				throwMBeanException(new RuntimeException("Conversion failure: " + params[i]));
			}

			e.setValue(params[i]);
			params[i] = e.getAsText();
		}

		try {
			// Find the right method.
			for (Method m : source.getClass().getMethods()) {
				if (!m.getName().equalsIgnoreCase(name)) {
					continue;
				}
				Class<?>[] methodParamTypes = m.getParameterTypes();
				if (methodParamTypes.length != params.length) {
					continue;
				}

				Object[] convertedParams = new Object[params.length];
				for (int i = 0; i < params.length; i++) {
					if (Iterable.class.isAssignableFrom(methodParamTypes[i])) {
						// Generics are not supported.
						convertedParams = null;
						break;
					}
					PropertyEditor e = getPropertyEditor(source.getClass(), "p" + i, methodParamTypes[i]);
					if (e == null) {
						convertedParams = null;
						break;
					}

					e.setAsText((String) params[i]);
					convertedParams[i] = e.getValue();
				}
				if (convertedParams == null) {
					continue;
				}

				return convertValue(m.getReturnType(), "returnValue", m.invoke(source, convertedParams), false);
			}

			// No methods matched.
			throw new IllegalArgumentException("Failed to find a matching operation: " + name);
		} catch (Throwable e) {
			throwMBeanException(e);
		}

		throw new IllegalStateException();
	}

	public final T getSource() {
		return source;
	}

	public final MBeanServer getServer() {
		return server;
	}

	public final ObjectName getName() {
		return name;
	}

	public final MBeanInfo getMBeanInfo() {
		return info;
	}

	public final AttributeList getAttributes(String names[]) {
		AttributeList answer = new AttributeList();
		for (int i = 0; i < names.length; i++) {
			try {
				answer.add(new Attribute(names[i], getAttribute(names[i])));
			} catch (Exception e) {
				// Ignore.
			}
		}
		return answer;
	}

	public final AttributeList setAttributes(AttributeList attributes) {
		// Prepare and return our response, eating all exceptions
		String names[] = new String[attributes.size()];
		int n = 0;
		Iterator<Object> items = attributes.iterator();
		while (items.hasNext()) {
			Attribute item = (Attribute) items.next();
			names[n++] = item.getName();
			try {
				setAttribute(item);
			} catch (Exception e) {
				// Ignore all exceptions
			}
		}

		return getAttributes(names);
	}

	public final void setManagedResource(Object resource, String type) throws InstanceNotFoundException, InvalidTargetObjectTypeException, MBeanException {
		throw new RuntimeOperationsException(new UnsupportedOperationException());

	}

	public final void setModelMBeanInfo(ModelMBeanInfo info) throws MBeanException {
		throw new RuntimeOperationsException(new UnsupportedOperationException());
	}

	@Override
	public final String toString() {
		return source.toString();
	}

	public void addAttributeChangeNotificationListener(NotificationListener listener, String name, Object handback) {
		// Do nothing
	}

	public void removeAttributeChangeNotificationListener(NotificationListener listener, String name) throws ListenerNotFoundException {
		// Do nothing
	}

	public void sendAttributeChangeNotification(AttributeChangeNotification notification) throws MBeanException {
		throw new RuntimeOperationsException(new UnsupportedOperationException());
	}

	public void sendAttributeChangeNotification(Attribute oldValue, Attribute newValue) throws MBeanException {
		throw new RuntimeOperationsException(new UnsupportedOperationException());
	}

	public void sendNotification(Notification notification) throws MBeanException {
		throw new RuntimeOperationsException(new UnsupportedOperationException());
	}

	public void sendNotification(String message) throws MBeanException {
		throw new RuntimeOperationsException(new UnsupportedOperationException());

	}

	public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException {
		// Do nothing
	}

	public MBeanNotificationInfo[] getNotificationInfo() {
		return new MBeanNotificationInfo[0];
	}

	public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
		// Do nothing
	}

	public void load() throws InstanceNotFoundException, MBeanException, RuntimeOperationsException {
		throw new RuntimeOperationsException(new UnsupportedOperationException());
	}

	public void store() throws InstanceNotFoundException, MBeanException, RuntimeOperationsException {
		throw new RuntimeOperationsException(new UnsupportedOperationException());
	}

	public final ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
		this.server = server;
		this.name = name;
		return name;
	}

	public final void postRegister(Boolean registrationDone) {
		if (registrationDone) {
			sources.put(name, source);
		}
	}

	public final void preDeregister() throws Exception {
		// Do nothing
	}

	public final void postDeregister() {
		sources.remove(name);
		this.server = null;
		this.name = null;
	}
	
	private MBeanInfo createModelMBeanInfo(T source) {
		String className = source.getClass().getName();
		String description = "";

		ModelMBeanConstructorInfo[] constructors = new ModelMBeanConstructorInfo[0];
		ModelMBeanNotificationInfo[] notifications = new ModelMBeanNotificationInfo[0];

		List<ModelMBeanAttributeInfo> attributes = new ArrayList<ModelMBeanAttributeInfo>();
		List<ModelMBeanOperationInfo> operations = new ArrayList<ModelMBeanOperationInfo>();

		addAttributes(attributes, source);
		addExtraAttributes(attributes);

		addOperations(operations, source);
		addExtraOperations(operations);
		operations.add(new ModelMBeanOperationInfo("unregisterMBean", "unregisterMBean", new MBeanParameterInfo[0], void.class.getName(), ModelMBeanOperationInfo.ACTION));

		return new ModelMBeanInfoSupport(className, description, attributes.toArray(new ModelMBeanAttributeInfo[attributes.size()]), constructors, operations
				.toArray(new ModelMBeanOperationInfo[operations.size()]), notifications);
	}

	private void addAttributes(List<ModelMBeanAttributeInfo> attributes, Object object) {
		addAttributes(attributes, object, object.getClass(), "");
	}

	private void addAttributes(List<ModelMBeanAttributeInfo> attributes, Object object, Class<?> type, String prefix) {

		PropertyDescriptor[] pdescs;
		try {
			pdescs = Introspector.getBeanInfo(type).getPropertyDescriptors();
		} catch (IntrospectionException e) {
			return;
		}

		for (PropertyDescriptor pdesc : pdescs) {
//			log.info(pdesc.getName());
			if(!exceptAttributes.contains(pdesc.getName())) continue;
			// Ignore a write-only property.
			if (pdesc.getReadMethod() == null) {
				continue;
			}

			// Ignore unmanageable property.
			String attrName = pdesc.getName();
			Class<?> attrType = pdesc.getPropertyType();
			if (attrName.equals("class")) {
				continue;
			}
			if (!isReadable(type, attrName)) {
				continue;
			}

			// Expand if possible.
			if (isExpandable(type, attrName)) {
				expandAttribute(attributes, object, prefix, pdesc);
				continue;
			}

			// Ordinary property.
			String fqan = prefix + attrName;
			boolean writable = isWritable(type, pdesc);
			attributes.add(new ModelMBeanAttributeInfo(fqan, convertType(object.getClass(), attrName, attrType, writable).getName(), pdesc.getShortDescription(), true, writable, false));

			propertyDescriptors.put(fqan, pdesc);
		}
	}

	private boolean isWritable(Class<?> type, PropertyDescriptor pdesc) {
		if (type == null) {
			throw new NullPointerException("type");
		}
		if (pdesc == null) {
			return false;
		}
		String attrName = pdesc.getName();
		Class<?> attrType = pdesc.getPropertyType();
		boolean writable = pdesc.getWriteMethod() != null || isWritable(type, attrName);
		if (getPropertyEditor(type, attrName, attrType) == null) {
			writable = false;
		}
		return writable;
	}

	private void expandAttribute(List<ModelMBeanAttributeInfo> attributes, Object object, String prefix, PropertyDescriptor pdesc) {
		Object property;
		String attrName = pdesc.getName();
		try {
			property = getAttribute(object, attrName, pdesc.getPropertyType());
		} catch (Exception e) {
			LOGGER.debug("Unexpected exception.", e);
			return;
		}

		if (property == null) {
			return;
		}

		addAttributes(attributes, property, property.getClass(), prefix + attrName + '.');
	}

	private void addOperations(List<ModelMBeanOperationInfo> operations, Object object) {

		for (Method m : object.getClass().getMethods()) {
			String mname = m.getName();
			
			if(!exceptOperations.contains(mname)) {
				continue;
			}	

			// Ignore getters and setters.
			if (mname.startsWith("is") || mname.startsWith("get") || mname.startsWith("set")) {
				continue;
			}

			// Ignore Object methods.
			if (mname.matches("(wait|notify|notifyAll|equals|compareTo|hashCode|clone)")) {
				continue;
			}
				
			

			// Ignore other user-defined non-operations.
			if (!isOperation(mname, m.getParameterTypes())) {
				continue;
			}

			List<MBeanParameterInfo> signature = new ArrayList<MBeanParameterInfo>();
			int i = 1;
			for (Class<?> paramType : m.getParameterTypes()) {
				String paramName = "p" + (i++);
				if (getPropertyEditor(source.getClass(), paramName, paramType) == null) {
					continue;
				}
				signature.add(new MBeanParameterInfo(paramName, convertType(null, null, paramType, true).getName(), paramName));
			}

			Class<?> returnType = convertType(null, null, m.getReturnType(), false);
			operations.add(new ModelMBeanOperationInfo(m.getName(), m.getName(), signature.toArray(new MBeanParameterInfo[signature.size()]), returnType.getName(), ModelMBeanOperationInfo.ACTION));
		}
	}

	private Object getParent(String fqan) throws OgnlException {
		Object parent;
		int dotIndex = fqan.lastIndexOf('.');
		if (dotIndex < 0) {
			parent = source;
		} else {
			parent = getAttribute(source, fqan.substring(0, dotIndex), null);
		}
		return parent;
	}

	private String getLeafAttributeName(String fqan) {
		int dotIndex = fqan.lastIndexOf('.');
		if (dotIndex < 0) {
			return fqan;
		}
		return fqan.substring(dotIndex + 1);
	}

	private Class<?> getAttributeClass(String signature) throws ClassNotFoundException {
		if (signature.equals(Boolean.TYPE.getName())) {
			return Boolean.TYPE;
		}
		if (signature.equals(Byte.TYPE.getName())) {
			return Byte.TYPE;
		}
		if (signature.equals(Character.TYPE.getName())) {
			return Character.TYPE;
		}
		if (signature.equals(Double.TYPE.getName())) {
			return Double.TYPE;
		}
		if (signature.equals(Float.TYPE.getName())) {
			return Float.TYPE;
		}
		if (signature.equals(Integer.TYPE.getName())) {
			return Integer.TYPE;
		}
		if (signature.equals(Long.TYPE.getName())) {
			return Long.TYPE;
		}
		if (signature.equals(Short.TYPE.getName())) {
			return Short.TYPE;
		}

		try {
			ClassLoader cl = Thread.currentThread().getContextClassLoader();
			if (cl != null) {
				return cl.loadClass(signature);
			}
		} catch (ClassNotFoundException e) {
			// Do nothing
		}

		return Class.forName(signature);
	}

	private Object getAttribute(Object object, String fqan, Class<?> attrType) throws OgnlException {
		Object property;
		OgnlContext ctx = (OgnlContext) Ognl.createDefaultContext(object);
		ctx.setTypeConverter(new OgnlTypeConverter());
		if (attrType == null) {
			property = Ognl.getValue(fqan, ctx, object);
		} else {
			property = Ognl.getValue(fqan, ctx, object, attrType);
		}
		return property;
	}

	private Class<?> convertType(Class<?> type, String attrName, Class<?> attrType, boolean writable) {
		if (attrName != null && (attrType == Long.class || attrType == long.class)) {
			if (attrName.endsWith("Time") && attrName.indexOf("Total") < 0 && attrName.indexOf("Min") < 0 && attrName.indexOf("Max") < 0 && attrName.indexOf("Avg") < 0
					&& attrName.indexOf("Average") < 0 && !propertyDescriptors.containsKey(attrName + "InMillis")) {
				return Date.class;
			}
		}

		if (!writable) {
			if (Collection.class.isAssignableFrom(attrType) || Map.class.isAssignableFrom(attrType)) {
				if (List.class.isAssignableFrom(attrType)) {
					return List.class;
				}
				if (Set.class.isAssignableFrom(attrType)) {
					return Set.class;
				}
				if (Map.class.isAssignableFrom(attrType)) {
					return Map.class;
				}
				return Collection.class;
			}

			if (attrType.isPrimitive() || Date.class.isAssignableFrom(attrType) || Boolean.class.isAssignableFrom(attrType) || Character.class.isAssignableFrom(attrType)
					|| Number.class.isAssignableFrom(attrType)) {
				if (attrName == null || !attrName.endsWith("InMillis") || !propertyDescriptors.containsKey(attrName.substring(0, attrName.length() - 8))) {
					return attrType;
				}
			}
		}

		return String.class;
	}

	private Object convertValue(Class<?> type, String attrName, Object v, boolean writable) {
		if (v == null) {
			return null;
		}

		if (attrName != null && v instanceof Long) {
			if (attrName.endsWith("Time") && attrName.indexOf("Total") < 0 && attrName.indexOf("Min") < 0 && attrName.indexOf("Max") < 0 && attrName.indexOf("Avg") < 0
					&& attrName.indexOf("Average") < 0 && !propertyDescriptors.containsKey(attrName + "InMillis")) {
				long time = (Long) v;
				if (time <= 0) {
					return null;
				}
				return new Date((Long) v);
			}
		}
		
		if (!writable) {
			if (v instanceof Collection || v instanceof Map) {
				if (v instanceof List) {
					return convertCollection(v, new ArrayList<Object>());
				}
				if (v instanceof Set) {
					return convertCollection(v, new LinkedHashSet<Object>());
				}
				if (v instanceof Map) {
					return convertCollection(v, new LinkedHashMap<Object, Object>());
				}
				return convertCollection(v, new ArrayList<Object>());
			}

			if (v instanceof Date || v instanceof Boolean || v instanceof Character || v instanceof Number) {
				if (attrName == null || !attrName.endsWith("InMillis") || !propertyDescriptors.containsKey(attrName.substring(0, attrName.length() - 8))) {
					return v;
				}
			}
		}

		PropertyEditor editor = getPropertyEditor(type, attrName, v.getClass());
		if (editor != null) {
			editor.setValue(v);
			return editor.getAsText();
		}

		return v.toString();
	}

	private Object convertCollection(Object src, Collection<Object> dst) {
		Collection<?> srcCol = (Collection<?>) src;
		for (Object e : srcCol) {
			Object convertedValue = convertValue(dst.getClass(), "element", e, false);
			if (e != null && convertedValue == null) {
				convertedValue = e.toString();
			}
			dst.add(convertedValue);
		}
		return dst;
	}

	private Object convertCollection(Object src, Map<Object, Object> dst) {
		Map<?, ?> srcCol = (Map<?, ?>) src;
		for (Map.Entry<?, ?> e : srcCol.entrySet()) {
			Object convertedKey = convertValue(dst.getClass(), "key", e.getKey(), false);
			Object convertedValue = convertValue(dst.getClass(), "value", e.getValue(), false);
			if (e.getKey() != null && convertedKey == null) {
				convertedKey = e.getKey().toString();
			}
			if (e.getValue() != null && convertedValue == null) {
				convertedKey = e.getValue().toString();
			}
			dst.put(convertedKey, convertedValue);
		}
		return dst;
	}

	private void throwMBeanException(Throwable e) throws MBeanException {
		if (e instanceof OgnlException) {
			OgnlException ognle = (OgnlException) e;
			if (ognle.getReason() != null) {
				throwMBeanException(ognle.getReason());
			} else {
				String message = ognle.getMessage();
				if (e instanceof NoSuchPropertyException) {
					message = "No such property: " + message;
				} else if (e instanceof ExpressionSyntaxException) {
					message = "Illegal expression syntax: " + message;
				} else if (e instanceof InappropriateExpressionException) {
					message = "Inappropriate expression: " + message;
				}
				e = new IllegalArgumentException(ognle.getMessage());
				e.setStackTrace(ognle.getStackTrace());
			}
		}
		if (e instanceof InvocationTargetException) {
			throwMBeanException(e.getCause());
		}

		LOGGER.warn("Unexpected exception.", e);
		if (e.getClass().getPackage().getName().matches("javax?\\..+")) {
			if (e instanceof Exception) {
				throw new MBeanException((Exception) e, e.getMessage());
			}

			throw new MBeanException(new RuntimeException(e), e.getMessage());
		}

		throw new MBeanException(new RuntimeException(e.getClass().getName() + ": " + e.getMessage()), e.getMessage());
	}

	protected Object getAttribute0(String fqan) throws Exception {
		throw new AttributeNotFoundException(fqan);
	}

	protected void setAttribute0(String attrName, Object attrValue) throws Exception {
		throw new AttributeNotFoundException(attrName);
	}

	protected Object invoke0(String name, Object params[], String signature[]) throws Exception {
		throw new NoSuchMethodException();
	}

	protected boolean isReadable(Class<?> type, String attrName) {

		if (ThreadPoolExecutor.class.isAssignableFrom(type) && attrName.equals("queue")) {
			return false;
		}

		return true;
	}

	public void setName(ObjectName name) {
		this.name = name;
	}

	protected boolean isWritable(Class<?> type, String attrName) {

		return false;
	}

	protected Class<?> getElementType(Class<?> type, String attrName) {
		return String.class;
	}

	protected Class<?> getMapKeyType(Class<?> type, String attrName) {
		return String.class;
	}

	protected Class<?> getMapValueType(Class<?> type, String attrName) {
		return String.class;
	}

	protected boolean isExpandable(Class<?> type, String attrName) {

		if (ThreadPoolExecutor.class.isAssignableFrom(type)) {
			if (attrName.equals("queueHandler")) {
				return true;
			}
		}
		return false;
	}

	protected boolean isOperation(String methodName, Class<?>[] paramTypes) {
		return true;
	}

	protected void addExtraAttributes(List<ModelMBeanAttributeInfo> attributes) {
		// Do nothing
	}

	protected void addExtraOperations(List<ModelMBeanOperationInfo> operations) {
		// Do nothing
	}

	protected PropertyEditor getPropertyEditor(Class<?> type, String attrName, Class<?> attrType) {
		if (type == null) {
			throw new NullPointerException("type");
		}

		if (attrName == null) {
			throw new NullPointerException("attrName");
		}

		if ((attrType == Long.class || attrType == long.class)) {
			if (attrName.endsWith("Time") && attrName.indexOf("Total") < 0 && attrName.indexOf("Min") < 0 && attrName.indexOf("Max") < 0 && attrName.indexOf("Avg") < 0
					&& attrName.indexOf("Average") < 0 && !propertyDescriptors.containsKey(attrName + "InMillis")) {
				return PropertyEditorFactory.getInstance(Date.class);
			}

			if (attrName.equals("id")) {
				return PropertyEditorFactory.getInstance(String.class);
			}
		}
/*
		if (List.class.isAssignableFrom(attrType)) {
			return new ListEditor(getElementType(type, attrName));
		}
*/
		if (Set.class.isAssignableFrom(attrType)) {
			return new SetEditor(getElementType(type, attrName));
		}

		if (Collection.class.isAssignableFrom(attrType)) {
			return new CollectionEditor(getElementType(type, attrName));
		}

		if (Map.class.isAssignableFrom(attrType)) {
			return new MapEditor(getMapKeyType(type, attrName), getMapValueType(type, attrName));
		}
		return PropertyEditorFactory.getInstance(attrType);
	}

	private class OgnlTypeConverter extends PropertyTypeConverter {
		@Override
		protected PropertyEditor getPropertyEditor(Class<?> type, String attrName, Class<?> attrType) {
			return ObjectMBean.this.getPropertyEditor(type, attrName, attrType);
		}
	}
}
